/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.gradleshell.shell.gradle;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.gradle.tooling.BuildLauncher;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;

/**
 *
 * @author jxcommerce<g2commerce@gmail.com>
 */
public class CommandService implements ICommandService {

    static final Logger LOG = Logger.getLogger(CommandService.class.getName());
    ProjectConnection projectConnection;
    String gradleHome ;//= "H:\\devtools\\gradle-1.9";
    String targetProjectDir ;//= "D:\\work\\prod_workspaces\\commerce_related\\prototype\\spring-boot-loaded";
    FileAlterationMonitor monitor ;
    public void init() {
        gradleHome = processGradleHome();
        targetProjectDir = processTargetProjectDir();

        GradleConnector gc = GradleConnector.newConnector().useInstallation(new File(gradleHome));
        projectConnection = gc.forProjectDirectory(new File(targetProjectDir)).connect();
    }

    void monitor(final StringBuilder msgBuffer) throws Exception {
        monitor = new FileAlterationMonitor(2);
        
        List<String> paths = getMonitorPath();
        if(LOG.isLoggable(Level.INFO)){
            LOG.log(Level.INFO, "monitoring source files path: ");
            LOG.log(Level.INFO, paths.toString());
        }
        
        List<FileAlterationObserver> faos = getFileAlterationObservers(paths);
        for(FileAlterationObserver fao : faos){
            monitor.addObserver(fao);
        }
        
        monitor.start();
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    LOG.info("Stopping monitor.");
                    stopMonitorSrc();
                } catch (Exception ignored) {
                }
            }
        }));
    }

    @Override
    public boolean isMonitorSrc() {
        return monitor!=null;
    }

    @Override
    public String stopMonitorSrc() {
        String retMsg = null;
        if(monitor!=null){
            try {
                monitor.stop();
                monitor = null;
                retMsg = "source file monitoring stopped";
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, null, ex);
                retMsg = "error while stop source file monitoring: "+ex.getMessage();
            }
        }else{
            retMsg = "source file monitoring not running";
        }
        return retMsg;
    }

    @Override
    public String startMonitorSrc() {
        if(monitor!=null){
            return "source file monitoring already running";
        }
        StringBuilder sb = new StringBuilder(100);
        String retMsg;
        try {
            monitor(sb);
            retMsg = sb.toString();
        } catch (Exception ex) {
            LOG.log(Level.SEVERE, null, ex);
            retMsg = "error while start source file monitoring: "+ex.getMessage();
        }
        return retMsg;
    }

    @Override
    public String runApp(String... jvmArgs) {
        String[] tasks = {"sl-run", "run"};
        return runGradleTasks(tasks, jvmArgs);
    }

    protected String runGradleTasks(String[] tasks, String[] jvmArgs) {
        if (tasks != null && tasks.length > 0) {
            try {
                BuildLauncher bl = projectConnection.newBuild();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                bl.setStandardOutput(out);
                bl.forTasks(tasks);
                if(jvmArgs!=null &&jvmArgs.length>0){
                    bl.setJvmArguments(jvmArgs);
                }
                bl.run();
                return new String(out.toByteArray(), "UTF-8");
            } catch (Throwable e) {
                LOG.log(Level.SEVERE, "error", e);
                return "";
            }
        }else{
            return "no task to run";
        }
    }

    private List<String> getMonitorPath() {
        List<String> ret = new ArrayList();
        ret.add(targetProjectDir + "\\src");
        //TODO multi projects
        return ret;
    }

    private List<FileAlterationObserver> getFileAlterationObservers(List<String> paths) {
        if(paths!=null &&paths.size()>0){
            List<FileAlterationObserver> ret = new ArrayList();
            for(String path : paths){
                FileAlterationObserver o = new FileAlterationObserver(new File(path));
                o.addListener(new FileChangeListener());
                ret.add(o);
            }
            return ret;
        }else{
            return Collections.EMPTY_LIST;
        }
    }

    private String processGradleHome() {
        String home = System.getProperty("GRADLE_HOME");
        if(StringUtils.isEmpty(home)){
            home = System.getenv("GRADLE_HOME");
        }
        if(StringUtils.isEmpty(home)){
            throw new IllegalStateException("GRADLE_HOME not defined");
        }
        return home;
    }

    private String processTargetProjectDir() {
        String ret = System.getProperty("user.dir");
        return ret;
    }

    class FileChangeListener implements FileAlterationListener {
        protected final Logger LOG = Logger.getLogger(getClass().getName());

        @Override
        public void onStart(FileAlterationObserver observer) {
        }

        @Override
        public void onDirectoryCreate(File directory) {
        }

        @Override
        public void onDirectoryChange(File directory) {
        }

        @Override
        public void onDirectoryDelete(File directory) {
        }

        @Override
        public void onFileCreate(File file) {
            processFileChange(file);
        }

        protected void processFileChange(File file) {
            List<String> tasks = new ArrayList();
            if(FilenameUtils.isExtension(file.getName(),"java")){
                tasks.add("compileJava") ;
            }
            else if(FilenameUtils.isExtension(file.getName(),"groovy")){
                tasks.add("compileGroovy");
            }
            else {
                tasks.add("processResources");
            }
            
            String[] taskArray  = new String[tasks.size()];
            try {
                runGradleTasks(tasks.toArray(taskArray), null);
            } catch (Exception e) {
                LOG.log(Level.SEVERE, "error", e);
            }
        }

        @Override
        public void onFileChange(File file) {
            processFileChange(file);
        }

        @Override
        public void onFileDelete(File file) {
            processFileChange(file);
        }

        @Override
        public void onStop(FileAlterationObserver observer) {
        }
    }
}
